﻿using System;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web.WebPages;
using FluentScheduler;
using log4net;
using QAS.Nekorama.Models;
using WebGrease.Css.Extensions;

namespace QAS.Nekorama.Common
{
    /// <summary>
    /// プロジェクト中で使うコンスタント
    /// </summary>
    public static class Constants
    {
        /// <summary>
        /// 特権
        /// </summary>
        public enum Privileges
        {
            BannedUser = 0,
            NewUser = 1,
            Veteran = 20,
            VoteUp = 30,
            Comment = 100,
            SetBounty = 150,
            VoteDown = 300,
            Report = 500,
            CreateTag = 3000,
            EditPost = 5000,
            Moderator = 10000
        }
        /// <summary>
        /// 評価
        /// </summary>
        public enum Reputation
        {
            QuestionVotedUp = 10,
            AnswerVotedUp = 20,
            AnswerAccepted = 30,
            AcceptAnswer = 2,
            QuestionVotedDown = -5,
            AnswerVotedDown = -5,
            VoteDownAnswer = -2,
            VoteDownQuestion = -2,
            DeleteQuestion = -50,
            DeleteAnswer = -25,
            ReportPost = -2,
            PostGetMarkedAsSpam = -100
        }

        public const int BOUNTY_DURATION = 7;
        public const int BAN_DURATION = 7;
        public const int COMMENT_CONTENT_MINIMUM = 20;
        public const int ASWER_CONTENT_MINIMUM = 30;
        public const int BOUNTY_MAXIMUM = 1000;
        public const int BOUNTY_MINIMUM = 100;
        public const int QUESTION_CONTENT_MINIMUM = 30;
        public const int QUESTION_TITLE_MINIMUM = 10;
        public const int NUMBER_TAG_MAXIMUM = 5;
        public const int SPAM_THRESHOLD = 5;
    }
    /// <summary>
    /// プロジェクト中で使うヘルパーの機能を含みます。
    /// </summary>
    public static class Helper
    {
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
        /// <summary>
        /// 評価を変更します。
        /// </summary>
        /// <param name="userid">ユーザーID</param>
        /// <param name="amount">評価の量</param>
        /// <returns>変更は成功したかどうか</returns>
        public static bool ChangeReputation(int userid, int amount)
        {
            Log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
            try
            {
                using (var db = new Entities())
                {
                    User user = db.Users.Find(userid);
                    user.Reputation += amount;
                    if (user.Reputation < 1)
                    {
                        Log.Debug(string.Format("Userid {0} reputation went below 1. Restore reputation to 1", userid));

                        user.Reputation = 1;
                    }
                    db.SaveChanges();
                    Log.Debug("End action " + MethodBase.GetCurrentMethod().Name);

                    return true;
                }
            }
            catch (InvalidOperationException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return false;
            }

            catch (Exception exception)
            {
                Log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// IDによってユーザーのオブジェクトを取り出します。
        /// </summary>
        /// <param name="id">ユーザーのID</param>
        /// <returns>ユーザーのオブジェクト</returns>
        public static User GetUserInfo(int id)
        {
            Log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
            User result = null;
            try
            {
                using (var db = new Entities())
                {
                    result = db.Users.SingleOrDefault(u => u.UserID == id);
                }
                Log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                return result;
            }
            catch (InvalidOperationException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return result;
            }

            catch (Exception exception)
            {
                Log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        ///  ユーザーのIDは読点で分離されるストリングにあるかどうか確認します。
        /// </summary>
        /// <param name="userid">ユーザーのID</param>
        /// <param name="collection">ストリング</param>
        /// <returns>含むかどうか</returns>
        public static bool IsInString(int userid, String collection)
        {
            Log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
            if (collection.IsEmpty())
            {
                Log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                return false;
            }
            bool result = false;
            string[] tokens = collection.Split(',');
            try
            {
                string res = tokens.SingleOrDefault(t => t.Trim() == userid.ToString());
                if (res != null)
                {
                    Log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                    result = true;
                }
                else
                {
                    Log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                    return false;
                }
            }
            catch (ArgumentNullException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return false;
            }
            catch (InvalidOperationException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return false;
            }

            catch (Exception exception)
            {
                Log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
            return result;
        }

        /// <summary>
        ///  この質問は容認された回答があるかどうか確認します。
        /// </summary>
        /// <param name="questionid">質問のID</param>
        /// <returns>「問題解決できる」と評価された回答あるかどうか</returns>
        public static bool HasAcceptedAnswer(int questionid)
        {
            Log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
            try
            {
                using (var db = new Entities())
                {
                    Answer ans =
                        db.Answers.SingleOrDefault(
                            a =>
                                a.QuestionID == questionid && a.IsAccepted == true && a.IsDeleted == false &&
                                a.IsSpam == false);
                    Log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                    return ans != null;
                }
            }
            catch (ArgumentNullException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return false;
            }
            catch (InvalidOperationException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return false;
            }

            catch (Exception exception)
            {
                Log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        ///  ユーザー名によってそのユーザーの声望を取り出します。
        /// </summary>
        /// <param name="name">ユーザー名</param>
        /// <returns>声望の数</returns>
        public static int GetReputation(string name)
        {
            int repcount = -1;
            Log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
            try
            {
                using (var db = new Entities())
                {
                    User user = db.Users.SingleOrDefault(u => u.DisplayName == name);
                    if (user != null)
                    {
                        repcount = user.Reputation;
                    }
                }
                Log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                return repcount;
            }
            catch (ArgumentNullException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return repcount;
            }
            catch (InvalidOperationException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return repcount;
            }

            catch (Exception exception)
            {
                Log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// ユーザーは禁止されたかどうかチェックします。
        /// </summary>
        /// <param name="id">ユーザーのID</param>
        /// <returns>禁止されたかどうか</returns>
        public static bool IsBanned(int id)
        {
            bool banned = false;
            Log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
            try
            {
                using (var db = new Entities())
                {
                    User user = db.Users.SingleOrDefault(u => u.UserID == id);
                    if (user != null && user.BanDuration != 0)
                    {
                        banned = true;
                    }
                }
                Log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                return banned;
            }
            catch (ArgumentNullException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return banned;
            }
            catch (InvalidOperationException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return banned;
            }

            catch (Exception exception)
            {
                Log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        ///  この回答は「問題解決できる」と評価された回答あるかどうか確認します。
        /// </summary>
        /// <param name="id">回答のID</param>
        /// <returns>「問題解決できる」と評価された回答であるかどうか</returns>
        public static bool IsAcceptedAnswer(int id)
        {
            Log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
            try
            {
                using (var db = new Entities())
                {
                    Log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                    return db.Answers.Find(id).IsAccepted != false && db.Answers.Find(id).IsAccepted != null;
                }
            }
            catch (InvalidOperationException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return false;
            }

            catch (Exception exception)
            {
                Log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// Htmlのストリングからプレーンテキストを抽出する
        /// </summary>
        /// <param name="source">Htmlのストリング</param>
        /// <returns>プレーンテキスト</returns>
        public static string Html2Text(string source)
        {
            try
            {
                string result = source.Replace("\r", " ");
                result = result.Replace("\n", " ");
                result = result.Replace("\t", string.Empty);
                result = Regex.Replace(result,
                    @"( )+", " ");
                result = Regex.Replace(result,
                    @"<( )*head([^>])*>", "<head>",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"(<( )*(/)( )*head( )*>)", "</head>",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    "(<head>).*(</head>)", string.Empty,
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    @"<( )*script([^>])*>", "<script>",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"(<( )*(/)( )*script( )*>)", "</script>",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"(<script>).*(</script>)", string.Empty,
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    @"<( )*style([^>])*>", "<style>",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"(<( )*(/)( )*style( )*>)", "</style>",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    "(<style>).*(</style>)", string.Empty,
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    @"<( )*td([^>])*>", "\t",
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    @"<( )*br( )*>", "\r",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"<( )*li( )*>", "\r",
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    @"<( )*div([^>])*>", "\r\r",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"<( )*tr([^>])*>", "\r\r",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"<( )*p([^>])*>", "\r\r",
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    @"<[^>]*>", string.Empty,
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    @" ", " ",
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    @"&bull;", " * ",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"&lsaquo;", "<",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"&rsaquo;", ">",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"&trade;", "(tm)",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"&frasl;", "/",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"&lt;", "<",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"&gt;", ">",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"&copy;", "(c)",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    @"&reg;", "(r)",
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    @"&(.{2,6});", string.Empty,
                    RegexOptions.IgnoreCase);

                result = result.Replace("\n", "\r");

                result = Regex.Replace(result,
                    "(\r)( )+(\r)", "\r\r",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    "(\t)( )+(\t)", "\t\t",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    "(\t)( )+(\r)", "\t\r",
                    RegexOptions.IgnoreCase);
                result = Regex.Replace(result,
                    "(\r)( )+(\t)", "\r\t",
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    "(\r)(\t)+(\r)", "\r\r",
                    RegexOptions.IgnoreCase);

                result = Regex.Replace(result,
                    "(\r)(\t)+", "\r\t",
                    RegexOptions.IgnoreCase);

                string breaks = "\r\r\r";

                string tabs = "\t\t\t\t\t";
                for (int index = 0; index < result.Length; index++)
                {
                    result = result.Replace(breaks, "\r\r");
                    result = result.Replace(tabs, "\t\t\t\t");
                    breaks = breaks + "\r";
                    tabs = tabs + "\t";
                }
                return result.Trim();
            }
            catch (ArgumentNullException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return source;
            }
            catch (ArgumentOutOfRangeException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return source;
            }
            catch (ArgumentException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return source;
            }
            catch (Exception exception)
            {
                Log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }

        /// <summary>
        /// 質問の回答の数を数えます。
        /// </summary>
        /// <param name="qid">質問のID</param>
        /// <returns>質問の回答の数</returns>
        public static int CountAnswer(int qid)
        {
            int count = 0;
            try
            {
                using (var db = new Entities())
                {
                    count =
                        db.Answers.Where(a => a.QuestionID == qid).Count(a => a.IsDeleted != true && a.IsSpam != true);
                }
                return count;
            }
            catch (ArgumentException exception)
            {
                Log.Error(
                    string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                        MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                return count;
            }
            catch (Exception exception)
            {
                Log.Fatal(
                    string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                    exception.Message, exception);
                throw;
            }
        }
    }

    /// <summary>
    ///  毎日のタクスを処理します。例え：禁止の期限と報奨の期限をカウントダウンします。
    /// </summary>
    public class BountyChecker : Registry
    {
        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        public BountyChecker()
        {
            Schedule<BountyCheckTask>().ToRunEvery(1).Days().At(2, 00);
        }

        /// <summary>
        ///   このクラスに毎日のタクスが定義されます。
        /// </summary>
        internal class BountyCheckTask : ITask
        {
            public void Execute()
            {
                CheckDuration();
            }

            /// <summary>
            /// この機能に毎日のタクスの論理が定義されます。
            /// </summary>
            public void CheckDuration()
            {
                Log.Debug("Start action " + MethodBase.GetCurrentMethod().Name);
                try
                {
                    using (var db = new Entities())
                    {
                        IQueryable<Bounty> bountylist = db.Bounties.Where(b => b.Duration > 0);
                        bountylist.ForEach(b => b.Duration -= 1);

                        foreach (Bounty bounty in bountylist)
                        {
                            if (bounty.Duration != 0) continue;
                            Answer answer = db.Answers.Where(
                                a => a.QuestionID == bounty.QuestionID && a.IsDeleted == false && a.IsSpam == false)
                                .OrderByDescending(a => a.VoteCount)
                                .FirstOrDefault();
                            if (answer == null) continue;
                            answer.IsAccepted = true;
                            db.Users.Find(answer.CreatedUser).Reputation += bounty.Amount/2;
                        }
                        Log.Debug("Update bounty duration.");

                        IQueryable<User> userlst = db.Users.Where(u => u.BanDuration > 0);
                        userlst.ForEach(u => u.BanDuration -= 1);
                        Log.Debug("Update ban duration. ");

                        db.Questions.ForEach(q => q.VisitedTodayUserList = string.Empty);
                        db.SaveChanges();
                        Log.Debug("End action " + MethodBase.GetCurrentMethod().Name);
                    }
                }
                catch (ArgumentNullException exception)
                {
                    Log.Error(
                        string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                            MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                }
                catch (InvalidOperationException exception)
                {
                    Log.Error(
                        string.Format("An {0} exception has occured at {1} action. ", exception.GetType(),
                            MethodBase.GetCurrentMethod().Name) + exception.Message, exception);
                }

                catch (Exception exception)
                {
                    Log.Fatal(
                        string.Format("A fatal exception has occured at {0} action ", MethodBase.GetCurrentMethod().Name) +
                        exception.Message, exception);
                    throw;
                }
            }
        }
    }

    
}